/*
 * Copyright 2013 Rivaldo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jety.commons.typechecker;

import org.jety.commons.typechecker.AbstractTypeChecker;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author Rivaldo
 */
public class GsonTypeChecker extends AbstractTypeChecker {

    @Override
    public boolean checkType(Class<?> clazz) throws IllegalArgumentException {
        return isValidType(clazz, null);
    }

    @Override
    public String getTypeName(Class<?> clazz) {

        String typeName = "struct";

        if (clazz == void.class || clazz == Void.class) {
            typeName = void.class.getName();
        } else if (clazz == boolean.class || Boolean.class == clazz) {
            typeName = boolean.class.getName();
        } else if (clazz == double.class || clazz == float.class || Double.class == clazz || Float.class == clazz) {
            typeName = double.class.getName();
        } else if (clazz == byte.class || clazz == char.class || clazz == int.class || clazz == short.class
                || clazz == long.class || clazz == Character.class || Number.class.isAssignableFrom(clazz)) {
            typeName = int.class.getName();
        } else if (clazz == String.class) {
            typeName = "string";
        } else if (clazz.isArray()) {
            typeName = "array";
        }

        return typeName;
    }

    //<editor-fold defaultstate="collapsed" desc="Métodos auxiliares">

    private boolean isValidType(Class<?> clazz, Set<Class<?>> visited) throws IllegalArgumentException {

        boolean valid = false;

        if (isCommonType(clazz)) {
            valid = true;
        } else if (isNotAllowedType(clazz)) {
            valid = false;
        //} else if (!hasZeroArgConstructor(clazz)){
          //  valid = false;
        } else if (hasCircularReference(clazz, visited)) {
            valid = false;
        } else {
            valid = checkFilds(clazz, visited);
        }

        return valid;
    }

    private boolean isCommonType(Class<?> clazz) {

        boolean common = false;

        if (clazz.isPrimitive()) {
            common = true;
        } else if (Boolean.class == clazz) {
            common = true;
        } else if (Number.class.isAssignableFrom(clazz)) {
            common = true;
        } else if (String.class == clazz) {
            common = true;
        } else if (Character.class == clazz) {
            common = true;
        } else if (Date.class == clazz) {
            common = true;
        } else if (clazz.isArray()) {
            return checkType(clazz.getComponentType());
        }

        return common;
    }

    private boolean isNotAllowedType(Class<?> clazz) throws IllegalArgumentException {

        boolean notAllowed = false;

        if (clazz.isAnonymousClass()) {
            notAllowed = true;
            throw new IllegalArgumentException("Anonymous class not allowed : " + clazz);
        } /*else if (Modifier.isInterface(clazz.getModifiers()) || Modifier.isAbstract(clazz.getModifiers())) {
            allowed = false;
            throw new IllegalArgumentException("Abstract class or interface not allowed : " + clazz);
        } else if (clazz.getTypeParameters().length > 0) {
            notAllowed = true;
            throw new IllegalArgumentException("Parametrized classes not allowed : " + clazz);
        }*/

        return notAllowed;
    }

    private boolean hasCircularReference(Class<?> clazz, Set<Class<?>> visited) {

        boolean circRef = false;
        visited = (visited == null ? new HashSet<Class<?>>() : visited);

        if (visited.contains(clazz)) {
            circRef = true;
            throw new IllegalArgumentException("Circular reference detected : " + clazz);
        }

        visited.add(clazz);
        return circRef;
    }

    private boolean checkFilds(Class<?> clazz, Set<Class<?>> visited) throws IllegalArgumentException {

        boolean valid = true;

        for (Field f : clazz.getDeclaredFields()) {

            int m = f.getModifiers();
            if (Modifier.isStatic(m) || Modifier.isTransient(m)) {
                continue;
            }

            /*if (Modifier.isFinal(m)) {
                valid = false;
                throw new IllegalArgumentException("Final field found : " + f);
            }*/

            try {
                valid = isValidType(f.getType(), visited);
                if (!valid) {
                    throw new IllegalArgumentException("Invalid field found : " + f);
                }
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Invalid field found : " + f, e);
            }
        }

        return valid;
    }

    private boolean hasZeroArgConstructor(Class<?> clazz) throws IllegalArgumentException {

        boolean valid = false;
        Constructor<?>[] constructors = clazz.getConstructors();

        if (constructors.length == 0) {
            valid = true;
        } else {

            for (Constructor constructor : constructors) {
                if (constructor.getParameterTypes().length == 0) {
                    valid = true;
                    break;
                }
            }
        }

        if (!valid) {
            throw new IllegalArgumentException("No zero-arg constructor found : " + clazz);
        }

        return valid;
    }

    //</editor-fold>
}
